/**
* Copyright 2009 Waag Society <http://www.waag.org>
* This file is part of Annotrax.
*
* Annotrax is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Annotrax is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Annotrax.
* If not, see http://www.gnu.org/licenses/gpl.html
*/

/**
 * jquery spin button plugin
 *
 * usage: $('input.spinButton').atSpinButtion()
 *
 *
 */
(function($){
	var AtSpinButton = function( element, config ){this.initialize( element, config )};
	AtSpinButton.prototype = {
		element: {},
		config: {},
		value: 0,

		initialize: function( element, config ){
			this.element = element;
			//alert(element)
			
			this.config  = $.extend( this.config, config );

			this.createSpinButton();

			this.setValue( $(this.element).attr('value') );

			//this.setValue( 0 );
			this.bindEvent( this.input, 'change', 'onValueChange' );
			this.bindEvent( this.buttonPlus, 'click', 'onButtonPlusClicked' );
			this.bindEvent( this.buttonMin, 'click', 'onButtonMinClicked' );

			this.bindEvent( this.buttonPlus, 'mousedown', 'onMouseDown' );
			this.bindEvent( this.buttonMin, 'mousedown', 'onMouseDown' );
			this.bindEvent( document, 'mouseup', 'onMouseUp' );

			this.bindEvent( this.input, 'DOMMouseScroll', 'onMouseWheel' );
			this.bindEvent( this.input, 'mousewheel', 'onMouseWheel' );
		},

		addEventTriggers: function(){
			for( var i = 0; i < this.config.triggers; i++ ){
				var trigger = this.config.triggers[i];

				if( typeof trigger.callback == 'string' ){
					var fn = this.config.event;
					fn = fn.charAt(0).toUpper() + fn.subString(1, fn.length);
				}



			}
		},
		/*
		_trigger: function(type, event, data) {
			var callback = this.config[type];

			event = $.Event(event);
			event.type = (type == this.widgetEventPrefix
					? type : this.widgetEventPrefix + type).toLowerCase();
			data = data || {};

			// copy original event properties over to the new event
			// this would happen if we could call $.event.fix instead of $.Event
			// but we don't have a way to force an event to be fixed multiple times
			if (event.originalEvent) {
				for (var i = $.event.props.length, prop; i;) {
					prop = $.event.props[--i];
					event[prop] = event.originalEvent[prop];
				}
			}

			this.element.trigger(event, data);

			return !($.isFunction(callback) && callback.call(this.element[0], event, data) === false
				|| event.isDefaultPrevented());
		},*/

		onMouseWheel: function( evt ){
			evt.stopPropagation();

			if( evt.wheelDelta > 0 ){
				this.onButtonPlusClicked();
			}
			else if( evt.wheelDelta <= 0 ){
				this.onButtonMinClicked();
			}
		},

		onMouseDown: function( evt ){
			var functionName = 'onButtonPlusClicked';
			if( $(evt.target).hasClass('atSpinButtonDown')){
				functionName = 'onButtonMinClicked';
			}

			var self = this;
			var f = function( evt ){self[functionName](evt, self);};

			this.timeOut = setTimeout( function(){
				clearTimeout( self.timeOut );
				self.timeOut = setInterval( f, 100 );
			}, 600 );
		},

		onMouseUp: function( evt ){
			if( this.timeOut ){
				clearInterval( this.timeOut );
			}
		},

		onValueChange: function( evt ){
			this.setValue( $(this.element).attr('value') );
		},

		onButtonPlusClicked: function( evt ){
			this.value = this.increaseValue( this.value );
			this.setValue( this.value );
		},

		onButtonMinClicked: function( evt ){
			this.value = this.decreaseValue( this.value );
			this.setValue( this.value );
		},

		/**
		 * create all the HTML needed.
		 */
		createSpinButton: function(){
			for( key in this.config.attr ){
				this.config.attr[key] = $(this.element).attr(key);
			}

			if( this.config.attr.id == '' ){
				this.config.attr.id = identify();
			}

			//var dim = this.calculateDimensions( this.element );
			width = $(this.element).width()
			
			// failproof css: position and sizes are set here.
			// note that height is a fixed value for simplicity sake
			this.input 		= $(this.element/*'<input/>'*/) //keep the original element
							.attr(this.config.attr)
							.addClass('atSpinButtonEnabled')
							.css({position: 'absolute', left: 0, top: 0, bottom: 0, right: '18px'});
			container = document.createElement("div")
			
			this.container	= $(container)
							.addClass('atSpinButtonWrapper')
							.css({position: 'relative', width: width + 'px', height: '22px'});
			this.buttonPlus	= $('<div>+</div>')
							.addClass('atSpinButtonUp')
							.css({position: 'absolute', top: 0, right: 0, bottom: '50%'});
			this.buttonMin	= $('<div>-</div>')
							.addClass('atSpinButtonDown')
							.css({position: 'absolute', bottom: 0, right: 0, top: '50%'});

			// add configured css
			$(this.input).css(this.config.style.spinButtonInput).css("width",width).css("border","none");
			$(this.buttonPlus).css(this.config.style.spinButtonButton);
			$(this.buttonMin).css(this.config.style.spinButtonButton);
			$(this.container).css(this.config.style.spinButtonContainer);

			//can't use replace because we need to move original input element
			input_container = this.element.parentNode
			container.appendChild(this.element)
			input_container.appendChild(container)
						
			$(this.container).append( this.buttonPlus ).append( this.buttonMin );
		
			this.element = this.input;
		},

		// return the actual dimensions of the element, including borders
		calculateDimensions: function( element ){
			var width = $(element).innerWidth();
			var height = $(element).innerHeight();

			// get border dimensions
			var borderLeft 	 = parseInt( $(element).css('border-left-width'));
			var borderRight  = parseInt( $(element).css('border-right-width'));
			var borderTop	 = parseInt( $(element).css('border-top-width') );
			var borderBottom = parseInt( $(element).css('border-bottom-width'));

			return{
				width: width + borderLeft + borderRight,
				height: height + borderTop + borderBottom
			}
		},

		/**
		 * makes sure this inside the event handler is the object
		 */
		bindEvent: function( element, eventName, functionName ){
			var self = this;
			var f = function( evt ){self[functionName](evt, self);};

			$(element).bind( eventName, f );
		},

		setValue: function( value ){
			var isNum 	= /^\d+$/;
			var isTime	= /(^\d{2}):(\d{2}):(\d{2}$)/;

			if( value == '' || typeof value == 'undefined' ){
				value = 0;
			}

			if( isNum.test( value ) == true ){
				$(this.input).attr('value', this.formatValue( value ));
				this.value = parseInt( value );
			}
			else if( isTime.test( value ) == true ){
				var time = value.split( ':' );
				value = 0;

				time[0] = parseInt(time[0]) * 3600;
				time[1] = parseInt(time[1]) * 60;
				time[2] = parseInt(time[2]);

				for( var i = 0; i < time.length; i++ ){
					value += time[i];
				}
				// no need to feedback numberformatting now do we?
				this.value = value;
			}
			else{
				$(this.input).attr( 'value', this.formatValue( this.value ) );
			}
			
			$(this.input).trigger(jQuery.Event("spinbuttonchange"), [this.value]);
			
			//this._trigger("spinbutton", event, /*uiHash*/);

			//$(this.input).trigger(jQuery.Event("atSpinButton"),[this.value]);
		},

		getValue: function(){
			if( this.value == '' ){
				this.value = 0;
			}
			return this.value;
		},

		increaseValue: function( value ){
			if( parseInt(value) + 1 > this.config.range.end  ){
				if( this.config.cycle == true ){
					value = this.config.range.start;
				}
				return value;
			}

			return value = parseInt( value )  + 1;
		},

		decreaseValue: function( value ){
			if( parseInt( value ) -1 < this.config.range.start ){
				if( this.config.cycle ){
					value = this.config.range.end;
				}
				return value;
			}

			return parseInt( value ) - 1;
		},

		/**
		 * format the value int HH:MM:SS
		 * Note: this can be overriden by giving a format function in the config values
		 */
		formatValue: function( value ){
			var format = [
				Math.floor( value / 3600 ),
				Math.floor( ( value % 3600 ) / 60 ),
				value % 60
			];

			for( var i = 0; i < format.length; i++ ){
				format[i] = format[i].toString().length == 1 ? '0' + format[i].toString() : format[i];
			}

			return format.join( ':' );
		}
	}

	$.fn.atSpinButton = function( settings ) {
		this.each(function() {
			var config = $.extend( true, {}, $.fn.atSpinButton.defaults, settings );
			var spin = new AtSpinButton( this, config );
		});
		return this;
	};

	$.fn.atSpinButton.defaults =  {
		range: {start: 0, end: 100},
		format: function( value ){},
		cycle: true,
		step: 1,
		style: {
			spinButtonButton: { font: 'bold 10px/10px courier, fixed', textAlign: 'center',
					width: '16px', backgroundColor: '#ccc', overflow: 'hidden',
					border: '1px outset #999', padding: 0, margin: 0
			},
			spinButtonContainer: {border: '1px solid #999'},
			spinButtonInput: {font: 'normal 12px/14px courier, fixed'}
		},
		attr: {
			'name': '',
			'value': 0,
			'id': '',
			'class': ''
		}
	}

	function identify(){
		return 'AtSpinbutton-' + ($('.atSpinButtonEnabled').length+1).toString();
	}

	function log( msg ){
		console.log( msg );
	}
})(jQuery);
